/***************************************************************************************************
 *
 * Project: HZFlashCards
 *
 * Class: HanZiDrawingSurface
 *
 * File name: HanZiDrawingSurface.java
 *
 *
 * SVN:
 *
 * $Rev$: $Date$: $LastChangedBy$:
 *
 * $HeadURL$:
 *
 **************************************************************************************************/

package com.eastmountain.chinese.gui;

import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.font.FontRenderContext;
import java.awt.font.LineBreakMeasurer;
import java.awt.font.TextAttribute;
import java.awt.font.TextLayout;
import java.awt.geom.Ellipse2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;
import java.util.Hashtable;
import java.util.Observable;
import java.util.Observer;
import java.util.Vector;

import javax.swing.JPanel;
import javax.swing.Timer;

import com.eastmountain.chinese.db.flashcards.records.ChineseCharacterProperties;
import com.eastmountain.chinese.db.flashcards.records.ChineseElementProperties;
import com.eastmountain.chinese.db.flashcards.records.ChineseWordProperties;
import com.eastmountain.chinese.observable.HZFlashcardsDataModel;
import com.eastmountain.chinese.radicals.Radicals;
import com.eastmountain.sound.MPEGPlayer;
import com.eastmountain.util.DisplayFormats;
import com.eastmountain.util.Helpers;

/***************************************************************************************************
 *
 * Class: HanZiDrawingSurface
 *
 **************************************************************************************************/

public class HanZiDrawingSurface extends JPanel implements ActionListener, Observer {

    /***************************************
     *     serialVersionUID  - Dec 9, 2007
     ***************************************/
    private static final long serialVersionUID = 7656682252762764641L;

    private HZFlashcardsDataModel hzFlashcardsDataModel = null;

    private Class currentFlashCardClass;
    
    private FlashCardSide currentHanZi   = new FlashCardSide( "HanZi", "" );
    private FlashCardSide currentPinYin  = new FlashCardSide( "PinYin", "" );
    private FlashCardSide currentEnglish = new FlashCardSide( "Definition", "" );

    private FlashCardSide[] flashCardSides = { currentHanZi, currentPinYin, currentEnglish };
    private int sideIndex = 0;

    private static final int HANZI_TEXT         = 0;
    private static final int PINYIN_TEXT        = 1;
    private static final int DEFINITION_TEXT    = 2;
    private static final int HANZI_RADICAL_TEXT = 3;

    // TODO move fonts to model

    private static Font hanZiFont = new Font( "Arial Unicode MS", Font.PLAIN, 40 );
    private static Font hanZiRadicalFont = new Font( "Arial Unicode MS", Font.PLAIN, 25 );
    private static Font pinYinFont = new Font( "Arial Unicode MS", Font.PLAIN, 40 );
    private static Font definitionFont = new Font( "Arial Unicode MS", Font.PLAIN, 24 );

    //  TODO reset all Fonts?
    
    private static Font activeHanZiFont = hanZiFont;

    private static int ARC_WIDTH  = 60;
    private static int ARC_HEIGHT = 60;

    private Timer swingTimer;

    private static int PAUSE_BEFORE_FLIP = 250; // milliseconds
    private static int FLIP_INTERVAL = 25; // milliseconds
    private double scaleX = 1.0;
    private static double scaleDelta = 0.05;
    private boolean doFirstPart = true;

    private static int BUFFERED_IMAGE_INSET = 10;

    public HanZiDrawingSurface() {

        hzFlashcardsDataModel = HZFlashcardsDataModel.getInstance();
        hzFlashcardsDataModel.addObserver( this );

        //setBackground( Color.GREEN.brighter() );

    }

    /***********************************************************************************
     *
     *  display
     *
     **********************************************************************************/

    public void display( ChineseElementProperties chineseElementProperties ) {

        System.out.println( "HanZiDrawingSurface().display() invoked" );

        int radicalNumber     = 0;
        int simplifiedRadical = 0;
        String chineseRadical = " ";
        String fileName       = "";
        ByteArrayInputStream sound = null;

        // String jaString = "\u65e5\u672c\u8a9e\u6587\u5b57\u5217";
        
        String chineseElement = extractElement( chineseElementProperties.getChineseElement() );
        
        if ( chineseElementProperties.getClass().equals( ChineseCharacterProperties.class ) ) {

            radicalNumber
                = ((ChineseCharacterProperties)chineseElementProperties ).getKangXiRadicalNumber();
            simplifiedRadical
                = ((ChineseCharacterProperties)chineseElementProperties ).getSimplifiedRadical();
            chineseRadical
                = Radicals.radicalNumberToUnicodeCharacter( radicalNumber, simplifiedRadical );
            
        } else if ( chineseElementProperties.getClass().equals( ChineseWordProperties.class ) ) {
            
            fileName
                = ((ChineseWordProperties)chineseElementProperties ).getFileName();
            sound
                = ((ChineseWordProperties)chineseElementProperties ).getSound();
            
            //  it seems we need to reset
            //  the InputStream back to the
            //  beginning if it has been
            //  played previously.
            
            if ( sound != null ) {
                
                sound.reset();                
                
            }

        } else {

            chineseRadical = " ";

        }

        System.out.println( "  +++++ Element class: " + chineseElementProperties.getClass().getSimpleName()
                            + "    Radical number: " + radicalNumber
                            + "    Simplified radical: " + simplifiedRadical
                            + "    Chinese character: "
                            + DisplayFormats.charToUnicodeString( chineseRadical.charAt( 0 ) ) );

        flashCardSides[ 0 ].setMainString( chineseElement );
        flashCardSides[ 0 ].setRadical( chineseRadical );
        flashCardSides[ 0 ].setFileName( fileName );
        flashCardSides[ 0 ].setSound( sound );
        flashCardSides[ 1 ].setMainString( extractElement( chineseElementProperties.getPinyin() ) );
        flashCardSides[ 2 ].setMainString( extractElement( chineseElementProperties.getEnglish() ) );

        resetFlipInfo();
        
        repaint();
        
        //  for now sounds only have one side
               
        if ( sideIndex == 0 ) {
            
            
            if ( sound != null ) {

                CheckForSound checkForSound
                = new CheckForSound( fileName, sound );

                Thread t = new Thread( checkForSound );
                t.start();
                
            }
            
        }
        
    }

    /*************************************************
     *
     *   update
     *
     *************************************************/

    public void update( Observable o, Object arg ) {

        Vector<ChineseElementProperties> chineLessonFlashCardSet;

        // TODO Handle argument checking within an aspect

        System.out.println( "!!! HanZiDrawingSurface.update() invoked  !!!!" );

        if ( arg == null ) {

            System.out.println( "   --- arg is null ---" );

            return;

        }

        System.out.println( "Class name: " + arg.getClass().getSimpleName() );
        
        if ( "Font".equals( arg.getClass().getSimpleName() ) ) {

            activeHanZiFont = (Font) arg;

        }

        repaint();

    }

    /***********************************************************************************
     *
     *  resetFlipInfo
     *
     **********************************************************************************/

    public void resetFlipInfo() {

        scaleX = 1.0;
        doFirstPart = true;
        sideIndex = 0;

        if ( swingTimer != null ) {

            swingTimer.stop();

        }

    }

    private String extractElement( String elementIn ) {

        String elementOut = "";

        elementOut = elementIn.trim();

        if ( "".equals( elementOut ) ) {

            elementOut = "Missing";
        }

        return elementOut;

    }

    public void turn() {

        // TODO maybe create SwingTimer only once

        swingTimer = new Timer( FLIP_INTERVAL, this );
        swingTimer.setInitialDelay( PAUSE_BEFORE_FLIP );

        scaleX = 1.0d;
        HanZiDrawingSurface.scaleDelta = 0.05;
        doFirstPart = true;

        swingTimer.start();

    }

    /***********************************************************************************
     *
     * paintComponent
     *
     **********************************************************************************/

    @Override
    protected void paintComponent( Graphics graphics ) {

        super.paintComponent( graphics );

        FontMetrics metrics;
        int textWidth;
        Point point;
        Ellipse2D.Double ellipse;

        Graphics2D g2D = (Graphics2D) graphics.create();

        int w = getWidth();
        int h = getHeight();

        BufferedImage bufferedImage = new BufferedImage( w, h, BufferedImage.TYPE_INT_RGB );

        Graphics2D graphics2D = (Graphics2D) bufferedImage.getGraphics();

        int bufferedImageWidth  = bufferedImage.getWidth();
        int bufferedImageHeight = bufferedImage.getHeight();

        graphics2D.setRenderingHint( RenderingHints.KEY_TEXT_ANTIALIASING,
                                     RenderingHints.VALUE_TEXT_ANTIALIAS_ON );

        graphics2D.setPaint( getBackground() );
        graphics2D.fill( new Rectangle2D.Double(0, 0, bufferedImageWidth, bufferedImageHeight ) );

        graphics2D.setFont( HanZiDrawingSurface.activeHanZiFont );
        metrics = graphics2D.getFontMetrics( HanZiDrawingSurface.activeHanZiFont );

        graphics2D.setFont( HanZiDrawingSurface.definitionFont );
        metrics = graphics2D.getFontMetrics( HanZiDrawingSurface.definitionFont );

        graphics2D.setColor( Color.BLACK );

        GradientPaint redtowhite
            = new GradientPaint( 0, 0, Color.RED,
                                 bufferedImageWidth,
                                 bufferedImageHeight, Color.WHITE );
        graphics2D.setPaint( redtowhite );
        ellipse = new Ellipse2D.Double(0, 0, bufferedImageWidth, bufferedImageHeight );
        graphics2D.fill( ellipse );

        graphics2D.setColor( Color.BLACK );

        textWidth = deriveTextWidthAndFont( graphics2D, sideIndex );

        //System.out.println( "w:   " + w + "  textWidth:   " + textWidth
        //                    + "  string length:   "
        //                    + flashCardSides[ sideIndex ].getMainString().length() );

        if  ( w - ( textWidth + HanZiDrawingSurface.BUFFERED_IMAGE_INSET * 2 ) >= 0 ) {

            point
                = Helpers.calculateFieldPosition( graphics2D, flashCardSides[ sideIndex ].getMainString(),
                                                  w, h, HanZiDrawingSurface.activeHanZiFont );
            graphics2D.drawString( flashCardSides[ sideIndex ].getMainString(),
                                            point.x, point.y );
            graphics2D.setFont( hanZiRadicalFont );
            textWidth = 25;
            graphics2D.drawString( flashCardSides[ sideIndex ].getRadical(),
                                   bufferedImageWidth - ( textWidth + 15 ) , bufferedImageHeight - 5 );

        } else {

            breakupText( graphics2D,
                         flashCardSides[ sideIndex ].getMainString(),
                         w );

        }

        int translateWidth = (int) (w * (1 - scaleX)) / 2;

        g2D.translate( translateWidth, 0 );
        g2D.scale( scaleX, 1.0 );
        g2D.drawImage( bufferedImage, 0, 0, this );

        g2D.dispose();
        graphics2D.dispose();

    }

    private int deriveTextWidthAndFont( Graphics2D graphics2D, int sideIndex ) {

        FontMetrics metrics;
        int textWidth = 0;

        // TODO Could use a single font and just change the size ????

        switch ( sideIndex ) {

            case HANZI_TEXT:

                graphics2D.setFont( HanZiDrawingSurface.activeHanZiFont );
                metrics = graphics2D.getFontMetrics();
                textWidth = metrics.stringWidth( flashCardSides[ sideIndex ].getMainString() );

                break;

            case PINYIN_TEXT:

                graphics2D.setFont( HanZiDrawingSurface.pinYinFont );
                metrics = graphics2D.getFontMetrics();
                textWidth = metrics.stringWidth( flashCardSides[ sideIndex ].getMainString() );

                break;

            case DEFINITION_TEXT:

                graphics2D.setFont( HanZiDrawingSurface.definitionFont );
                metrics = graphics2D.getFontMetrics();
                textWidth = metrics.stringWidth( flashCardSides[ sideIndex ].getMainString() );

                break;

            case HANZI_RADICAL_TEXT:

                graphics2D.setFont( HanZiDrawingSurface.hanZiRadicalFont );
                metrics = graphics2D.getFontMetrics();
                textWidth = metrics.stringWidth( flashCardSides[ sideIndex ].getMainString() );

                break;

            default:

                break;

        }

        return textWidth;

    }

    /***********************************************************************************
     *
     *  breakupText
     *
     **********************************************************************************/

    private void breakupText( Graphics2D graphics2D, String test, int surfaceWidth ) {

        LineBreakMeasurer lineMeasurer = null;

        // index of the first character in the paragraph.

        int paragraphStart = 0;

        // index of the first character after the end of the paragraph.

        int paragraphEnd = 0;

        final Hashtable<TextAttribute, Object> map = new Hashtable<TextAttribute, Object>();

        map.put( TextAttribute.FAMILY, HanZiDrawingSurface.definitionFont.getFamily() );
        map.put( TextAttribute.SIZE, HanZiDrawingSurface.definitionFont.getSize2D() );
        map.put( TextAttribute.FOREGROUND, Color.YELLOW );

        final AttributedString vanGogh = new AttributedString(
                                        "Many people believe that Vincent van Gogh painted his best works "
                                      + "during the two-year period he spent in Provence. Here is where he "
                                      + "painted The Starry Night--which some consider to be his greatest "
                                      + "work of all. However, as his artistic brilliance reached new "
                                      + "heights in Provence, his physical and mental health plummeted. ",
                                        map );

        final AttributedString chineseElement = new AttributedString( test, map );

        // Create a new LineBreakMeasurer from the paragraph.
        // It will be cached and re-used.

        if ( lineMeasurer == null ) {

            AttributedCharacterIterator paragraph = chineseElement.getIterator();
            paragraphStart = paragraph.getBeginIndex();
            paragraphEnd = paragraph.getEndIndex();
            FontRenderContext frc = graphics2D.getFontRenderContext();
            lineMeasurer = new LineBreakMeasurer( paragraph, frc );

        }

        // Set break width to width of Component.

        float breakWidth = surfaceWidth;
        float drawPosY = 0;

        // Set position to the index of the first character in the paragraph.

        lineMeasurer.setPosition( paragraphStart );

        // Get lines until the entire paragraph has been displayed.

        while ( lineMeasurer.getPosition() < paragraphEnd ) {

            // Retrieve next layout. A cleverer program would also cache
            // these layouts until the component is re-sized.

            TextLayout layout = lineMeasurer.nextLayout( breakWidth );

            // Compute pen x position. If the paragraph is right-to-left we
            // will align the TextLayouts to the right edge of the panel.
            // Note: this won't occur for the English text in this sample.
            // Note: drawPosX is always where the LEFT of the text is placed.

            float drawPosX = layout.isLeftToRight() ? 0 : breakWidth - layout.getAdvance();

            // Move y-coordinate by the ascent of the layout.

            drawPosY += layout.getAscent();

            // Draw the TextLayout at (drawPosX, drawPosY).

            layout.draw( graphics2D, drawPosX, drawPosY );

            // Move y-coordinate in preparation for next layout.

            drawPosY += layout.getDescent() + layout.getLeading();

        }

    }

    /***********************************************************************************
     *
     *  actionPerformed - invoked by a Swing Timer
     *
     **********************************************************************************/

    public void actionPerformed( ActionEvent event ) {

        scaleX = scaleX - HanZiDrawingSurface.scaleDelta;

        //System.out.println( "scaleX: " + scaleX );

        if ( doFirstPart ) {

            //currentSide = currentHanZi;

            if ( scaleX < 0 ) {

                //System.out.println( "scaleX: " + scaleX + " - Stopping" );

                // swingTimer.stop();

                scaleX = 0.0d;
                HanZiDrawingSurface.scaleDelta = -HanZiDrawingSurface.scaleDelta;

                sideIndex = (sideIndex + 1) % flashCardSides.length;

                doFirstPart = false;

            } else if ( (scaleX < 0.05) && (scaleX > 0.0) ) {

                scaleX = 0.05;

                repaint();

            } else {

                repaint();

            }

        } else {

            //currentSide = currentPinYin;

            if ( scaleX > 1.0 ) {

                //System.out.println( "scaleX: " + scaleX + " - Stopping" );

                scaleX = 1.0;

                // sideIndex = ( sideIndex + 1 ) % sides.length;

                swingTimer.stop();

            } else if ( (scaleX > 0.95) && (scaleX < 1.0) ) {

                scaleX = 0.95;

                repaint();

            } else {

                repaint();

            }

        }

    }

    private void showEllipsePoints( Ellipse2D.Double ellipse ) {

        // TODO this doesn't really show the points of the elipse

        double[] coords = { 0, 0, 0, 0, 0, 0 };
        int segmentType;

        System.out.println( "\n#### Begin Path\n" );

        PathIterator iterator = ellipse.getPathIterator( null );

        iterator.next();

        while ( ! iterator.isDone() ) {

            segmentType = iterator.currentSegment( coords );

            //if ( segmentType == PathIterator.SEG_MOVETO ) {

                System.out.println( "     x = " + (int)coords[ 0 ] + " y = " + (int)coords[ 1 ] );

            //}

            iterator.next();

        }

        System.out.println( "\n#### End Path\n" );

    }

    public void setCurrentFlashCardClass( Class currentFlashCardClass ) {
        
        this.currentFlashCardClass = currentFlashCardClass;
        
    }

}

/***********************************************************************************
 *
 *  Class CheckForSound
 *
 **********************************************************************************/

class CheckForSound implements Runnable {

    private String fileName;
    private InputStream sound = null;

    /***********************************************************************************************
     *
     *   CheckForSound
     *
     **********************************************************************************************/

    public CheckForSound( String fileName, InputStream sound ) {

        this.fileName = fileName;
        this.sound = sound;
        
    }

    /***********************************************************************************************
     *
     *   run
     *
     **********************************************************************************************/

    public void run() {

        if ( sound != null ) {
            
            System.out.println( "***** About to play sound!!!\n" );
            
            try {
                Thread.sleep( 500 );
            } catch ( InterruptedException e ) {
                
                System.out.println( "CheckForSound's sleep interrupted" );
                
                e.printStackTrace();
                
            }

            MPEGPlayer.play( fileName, sound );

        }

    }

}

/***********************************************************************************
 *
 *  Class FlashCardSide
 *
 **********************************************************************************/

class FlashCardSide {

    private String                 mainString = "";
    private String                 radical    = "";
    private String                 fileName;
    private ByteArrayInputStream   sound;


    public FlashCardSide( String mainString, String radical ) {

        this.mainString = mainString;
        this.radical    = radical;

    }

    public String getMainString() {

        return mainString;
    }

    public void setMainString( String mainString ) {

        this.mainString = mainString;
    }

    public String getRadical() {

        return radical;
    }

    public void setRadical( String radical ) {

        this.radical = radical;
    }

    public String getFileName() {
        return fileName;
    }

    public void setFileName( String fileName ) {
        this.fileName = fileName;
    }

    public ByteArrayInputStream getSound() {
        return sound;
    }

    public void setSound( ByteArrayInputStream sound ) {
        this.sound = sound;
    }

}

